﻿using System;
using System.Linq;
using System.Linq.Expressions;

using Norm;

using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Exceptions;
using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.DataAccess;
using Fuse8.DomainFramework.CacheService;

namespace Fuse8.DomainFramework.MongoDbWrapper
{
    public class MongoDbEntityDataRepository<TEntity, TRepository> : BaseEntityDataRepository<TEntity, string, TRepository>
        where TEntity : class, IEntityObject<string>, new()
        where TRepository : class, IMongoDbRepositoryEntity, new()
    {
        #region Properties

        /// <summary>
        /// 
        /// </summary>
        protected virtual IMongo CurrentDb
        {
            get
            {
                var unitOfWork = UnitOfWorkController<TEntity>.GetCurrentUnitOfWork() as MongoDbUnitOfWork<TEntity>;

                if (unitOfWork == null)
                {
                    throw new UnitOfWorkNotDefinedException();
                }

                return unitOfWork.CurrentDb;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override DataRepositoryCacheOptions CacheOptions
        {
            get
            {
                return DataRepositoryCacheOptions.NoCache;
            }
        }

        #endregion

        #region BaseEntityDataRepository override

        protected override void AddToRepository(TRepository repositoryEntity)
        {
            Guard.ArgumentNotNull(repositoryEntity, "repositoryEntity");
            
            CurrentDb.GetCollection<TRepository>().Insert(repositoryEntity);
        }

        protected override void SaveToRepository(TRepository repositoryEntity)
        {
            Guard.ArgumentNotNull(repositoryEntity, "repositoryEntity");

            CurrentDb.GetCollection<TRepository>().UpdateOne(repositoryEntity, repositoryEntity);
        }

        protected override void DeleteFromRepository(TRepository repositoryEntity)
        {
            Guard.ArgumentNotNull(repositoryEntity, "repositoryEntity");

            CurrentDb.GetCollection<TRepository>().Delete(repositoryEntity);
        }

        protected override void DeleteFromRepository(string id)
        {
            var entity = FindOne(id);

            if (entity != null)
            {
                var repositoryEntity = Mapper.CreateRepositoryEntity();

                Mapper.ToRepositoryEntity(entity, repositoryEntity);

                if (repositoryEntity != null)
                {
                    DeleteFromRepository(repositoryEntity);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public override TEntity FindOne(Expression<Func<TEntity, bool>> expression)
        {
            Guard.ArgumentNotNull(expression, "expression");

            return CreateMongoDbQuery(expression).FirstOrDefault();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public override IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression)
        {
            Guard.ArgumentNotNull(expression, "expression");

            return CreateMongoDbQuery(expression);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override IQueryable<TEntity> FindAll()
        {
            return CreateMongoDbQuery(null);
        }

        protected override IQueryable<TEntity> AsQueryable()
        {
            return CreateMongoDbQuery(null);
        }

        #endregion

        #region Methods

        protected virtual IQueryable<TEntity> CreateMongoDbQuery(Expression<Func<TEntity, bool>> expression)
        {
            Func<TRepository, TEntity> translator = (p) =>
                {
                    var entity = Mapper.CreateEntity();

                    Mapper.FromRepositoryEntity(p, entity);

                    return entity;
                };

            var result = CurrentDb.GetCollection<TRepository>()
                .AsQueryable()
                .Select(p => translator(p));

            return expression != null ?
                (result.Where(expression)) :
                (result);
        }

        #endregion
    }
}
